跳到主要内容

Go 使用 JWT

使用的是 go-jwt 开源库

go get -u github.com/golang-jwt/jwt

编写 JWT 工具

快速编写一个 JWT 工具

package util

import (
"github.com/golang-jwt/jwt"
"std01/pkg/setting"
"time"
)

// Claims 一般不在 JWT 存重要信息
type Claims struct {
Username string `json:"username"`
jwt.StandardClaims
}

var jwtSecret = []byte(setting.JwtSecret)

// GenerateToken 生成 Token
func GenerateToken(username string) (string, error) {
nowTime := time.Now()
expireTime := nowTime.Add(3 * time.Hour)

claims := Claims{
username,
jwt.StandardClaims {
ExpiresAt : expireTime.Unix(),
Issuer : "gin-blog",
},
}

tokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
// SignedString 该方法内部生成签名字符串,再用于获取完整、已签名的token
token, err := tokenClaims.SignedString(jwtSecret)
return token, err
}


// ParseToken 解析 Token
func ParseToken(token string) (*Claims, error) {
// ParseWithClaims 用于解析鉴权的声明,方法内部主要是具体的解码和校验的过程,最终返回*Token
tokenClaims, err := jwt.ParseWithClaims(token, &Claims{}, func(token *jwt.Token) (interface{}, error) {
return jwtSecret, nil
})

if tokenClaims != nil {
// Valid() 验证基于时间的声明exp, iat, nbf,注意如果没有任何声明在令牌中,仍然会被认为是有效的。并且对于时区偏差没有计算方法
if claims, ok := tokenClaims.Claims.(*Claims); ok && tokenClaims.Valid {
return claims, nil
}
}

return nil, err
}

整合进 Gin

在 middleware 目录下(没有就自己创建)新建 jwt 目录,新建 jwt.go 文件,写入内容:

package jwt

import (
"github.com/gin-gonic/gin"
"net/http"
"std01/pkg/e"
"std01/pkg/util"
"time"
)

func JWT() gin.HandlerFunc {
return func(c *gin.Context) {
var code int
var data interface{}

code = e.SUCCESS
token := c.Query("token")
if token == "" {
code = e.INVALID_PARAMS
} else {
claims, err := util.ParseToken(token)
if err != nil {
code = e.ERROR_AUTH_CHECK_TOKEN_FAIL
} else if time.Now().Unix() > claims.ExpiresAt {
code = e.ERROR_AUTH_CHECK_TOKEN_TIMEOUT
}
}

if code != e.SUCCESS {
c.JSON(http.StatusUnauthorized, gin.H{
"code" : code,
"msg" : e.GetMsg(code),
"data" : data,
})

c.Abort()
return
}

c.Next()
}
}

然后再在这个路由上整合上这个 JWT 中间件

func InitRouter() *gin.Engine {
r := gin.New()

r.Use(gin.Logger())

r.Use(gin.Recovery())

gin.SetMode(setting.RunMode)

r.GET("/auth", api.GetAuth)

apiv1 := r.Group("/api/v1")
apiv1.Use(jwt.JWT()) // ⭐
{
...
}

return r
}

取得 Token 的 API

上面把这个中间件整合进 Gin 后,哪里取得 JWT 呢?

所以这里编写一个用来取得 JWT 的 API

先在持久层校验账号密码,在 models 下新建 auth.go 文件,写入内容:

type Auth struct {
ID int `gorm:"primary_key" json:"id"`
Username string `json:"username"`
Password string `json:"password"`
}

func CheckAuth(username, password string) bool {
var auth Auth
db.Select("id").Where(Auth{Username : username, Password : password}).First(&auth)
if auth.ID > 0 {
return true
}

return false
}

然后编写一个 Controller

package api

import (
"github.com/astaxie/beego/validation"
"github.com/gin-gonic/gin"
"log"
"net/http"
"std01/models"
"std01/pkg/e"
"std01/pkg/util"
)

type auth struct {
Username string `valid:"Required; MaxSize(50)"`
Password string `valid:"Required; MaxSize(50)"`
}

func GetAuth(c *gin.Context) {
username := c.Query("username")
password := c.Query("password")

valid := validation.Validation{}
a := auth{Username: username, Password: password}
ok, _ := valid.Valid(&a)

data := make(map[string]interface{})
code := e.INVALID_PARAMS
if ok {
isExist := models.CheckAuth(username, password)
if isExist {
token, err := util.GenerateToken(username)
if err != nil {
code = e.ERROR_AUTH_TOKEN
} else {
data["token"] = token

code = e.SUCCESS
}

} else {
code = e.ERROR_AUTH
}
} else {
for _, err := range valid.Errors {
log.Println(err.Key, err.Message)
}
}

c.JSON(http.StatusOK, gin.H{
"code" : code,
"msg" : e.GetMsg(code),
"data" : data,
})
}

在路由处添加这个 Controller

func InitRouter() *gin.Engine {
r := gin.New()

r.Use(gin.Logger())

r.Use(gin.Recovery())

gin.SetMode(setting.RunMode)

r.GET("/auth", api.GetAuth)

apiv1 := r.Group("/api/v1")
{
...
}

return r
}

Reference

「连载五」使用 JWT 进行身份校验